﻿// -----------------------------------------------------------------------
// <copyright file="RefColumnDto.cs" company="JSC Centido">
//  Copyright (c) JSC Centido. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Xml;
using System.Xml.Schema;

namespace Centido.Core.Dto
{
    /// <summary>
    ///   A data transfer object for a reference table column.
    /// </summary>
    public class RefColumnDto : ColumnDto
    {
        /// <summary>
        ///   The name of the xml tag that is used to store this object.
        /// </summary>
        public const string XmlTag = "refColumn";

        /// <summary>
        ///   The current xml schema version.
        /// </summary>
        public const string CurrentXmlSchemaVersion = "1";

        /// <summary>
        ///   The name of the xml attribute that is used to store a column default value.
        /// </summary>
        public const string DefaultValueAttribute = "defaultValue";

        /// <summary>
        ///   The name of the xml attribute that is used to store the need for reference checking.
        /// </summary>
        public const string HasReferenceCheckingAttribute = "hasRefChecking";

        /// <summary>
        ///   The name of the tag that is used to store the reference target list.
        /// </summary>
        public const string TargetListTag = "targets";


        /// <summary>
        ///   The list reference column targets.
        /// </summary>
        private readonly List<RefTargetDto> _refTargets = new List<RefTargetDto>();


        /// <summary>
        ///   Gets the column type.
        /// </summary>
        public override ColumnType ColumnType
        {
            get { return ColumnType.Reference; }
        }


        /// <summary>
        ///   Gets or sets the column default value.
        /// </summary>
        public Guid? DefaultValue { get; set; }


        /// <summary>
        ///   Gets or sets the need for reference checking.
        /// </summary>
        public bool? HasReferenceChecking { get; set; }


        /// <summary>
        ///   Gets the column reference targets.
        /// </summary>
        public ICollection<RefTargetDto> Targets
        {
            get { return _refTargets; }
        }


        /// <summary>
        ///   Adds the specified reference target to the column reference target list.
        /// </summary>
        /// <param name="refTargetDto">The reference target to be added.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="refTargetDto"/> is null.</exception>
        public void AddTarget(RefTargetDto refTargetDto)
        {
            if (refTargetDto == null)
                throw new ArgumentNullException("refTargetDto");

            if (!_refTargets.Contains(refTargetDto))
                _refTargets.Add(refTargetDto);
        }


        /// <summary>
        ///   Removes the specified reference target from the column reference target list.
        /// </summary>
        /// <param name="refTargetDto">The reference target to be deleted.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="refTargetDto"/> is null.</exception>
        public void DeleteTarget(RefTargetDto refTargetDto)
        {
            if (refTargetDto == null)
                throw new ArgumentNullException("refTargetDto");

            if (_refTargets.Contains(refTargetDto))
                _refTargets.Remove(refTargetDto);
        }


        /// <summary>
        ///   This method is reserved and should not be used. When implementing the IXmlSerializable interface, you should return null (Nothing in Visual Basic) from this method, and instead, if specifying a custom schema is required, apply the <see cref="T:System.Xml.Serialization.XmlSchemaProviderAttribute"/> to the class.
        /// </summary>
        /// <returns>
        ///   An <see cref="T:System.Xml.Schema.XmlSchema"/> that describes the XML representation of the object that is produced by the <see cref="M:System.Xml.Serialization.IXmlSerializable.WriteXml(System.Xml.XmlWriter)"/> method and consumed by the <see cref="M:System.Xml.Serialization.IXmlSerializable.ReadXml(System.Xml.XmlReader)"/> method.
        /// </returns>
        public override XmlSchema GetSchema()
        {
            return null;
        }


        /// <summary>
        ///   Generates an object from its XML representation.
        /// </summary>
        /// <param name="reader">The <see cref="T:System.Xml.XmlReader"/> stream from which the object is deserialized. 
        /// </param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="reader"/> is null.</exception>
        public override void ReadXml(XmlReader reader)
        {
            if (reader == null)
                throw new ArgumentNullException("reader");

            reader.MoveToContent();

            // Reading the xml schema version
            var xmlSchemaVersion = ReadStringAttribute(reader, XmlSchemaVersionTag, isRequired: false);

            // If the xml schema version is not defined
            if (string.IsNullOrEmpty(xmlSchemaVersion)
                // or if it is the current xml schema version
                || xmlSchemaVersion == CurrentXmlSchemaVersion)
            {
                // Reading the column name
                Name = ReadStringAttribute(reader, NameAttribute);

                // Reading the display name
                DisplayName = ReadStringAttribute(reader, DisplayNameAttribute, isRequired: false);

                // Reading the description
                Description = ReadStringAttribute(reader, DescriptionAttribute, isRequired: false);

                // Reading the order index
                OrderIndex = ReadIntAttribute(reader, OrderIndexAttribute, isRequired: false);

                // Reading the column nullability
                IsNotNull = ReadBoolAttribute(reader, IsNotNullAttribute, isRequired: false);

                // Reading the default value
                DefaultValue = ReadGuidAttribute(reader, DefaultValueAttribute, isRequired: false);

                // Reading the reference checking
                HasReferenceChecking = ReadBoolAttribute(reader, HasReferenceCheckingAttribute, isRequired: false);

                // Clearing the list of targets
                _refTargets.Clear();

                var isEmptyElement = reader.IsEmptyElement;

                reader.ReadStartElement();

                if (!isEmptyElement)
                {
                    // The target list is specified
                    if (reader.LocalName == TargetListTag)
                    {
                        isEmptyElement = reader.IsEmptyElement;

                        reader.ReadStartElement();

                        if (!isEmptyElement)
                        {
                            // Reading the reference targets
                            while (reader.LocalName == RefTargetDto.XmlTag)
                            {
                                var refTargetDto = new RefTargetDto();

                                refTargetDto.ReadXml(reader);

                                _refTargets.Add(refTargetDto);
                            }

                            reader.ReadEndElement();
                        }
                    }

                    reader.ReadEndElement();
                }
            }
            else
                throw new ArgumentException(string.Format(
                    "The specified xml schema version is not supported (node = {0}, version = {1}).",
                    reader.Name,
                    xmlSchemaVersion));
        }


        /// <summary>
        ///   Converts an object into its XML representation.
        /// </summary>
        /// <param name="writer">The <see cref="T:System.Xml.XmlWriter"/> stream to which the object is serialized. 
        /// </param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="writer"/> is null.</exception>
        public override void WriteXml(XmlWriter writer)
        {
            if (writer == null)
                throw new ArgumentNullException("writer");

            writer.WriteStartElement(XmlTag);

            // The xml schema version
            writer.WriteAttributeString(XmlSchemaVersionTag, CurrentXmlSchemaVersion);

            // The column name
            if (!string.IsNullOrEmpty(Name))
                writer.WriteAttributeString(NameAttribute, Name);

            // The column display name
            if (!string.IsNullOrEmpty(DisplayName))
                writer.WriteAttributeString(DisplayNameAttribute, DisplayName);

            // The column description
            if (!string.IsNullOrEmpty(Description))
                writer.WriteAttributeString(DescriptionAttribute, Description);

            // The column order index
            if (OrderIndex.HasValue)
                writer.WriteAttributeString(OrderIndexAttribute, 
                    OrderIndex.Value.ToString(CultureInfo.InvariantCulture));

            // The column nullability
            if (IsNotNull.HasValue)
                writer.WriteAttributeString(IsNotNullAttribute, 
                    IsNotNull.Value.ToString(CultureInfo.InvariantCulture));

            // The column default value
            if (DefaultValue.HasValue)
                writer.WriteAttributeString(DefaultValueAttribute,
                    DefaultValue.ToString());

            // The column reference checking
            if (HasReferenceChecking.HasValue)
                writer.WriteAttributeString(HasReferenceCheckingAttribute, 
                    HasReferenceChecking.Value.ToString(CultureInfo.InvariantCulture));

            // If there are enumeration column members
            if (_refTargets.Count > 0)
            {
                writer.WriteStartElement(TargetListTag);

                // Writing the reference targets
                foreach (var targetDto in _refTargets)
                    targetDto.WriteXml(writer);

                writer.WriteEndElement();
            }

            writer.WriteEndElement();
        }


        /// <summary>
        ///   Copies the properties of the data transfer object to the specified reference column.
        /// </summary>
        /// <param name="column">The reference column.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="column"/> is null.</exception>
        /// <exception cref="ArgumentException">Thrown when the column has the type other than reference.</exception>
        public override void CopyTo(Column column)
        {
            if (column == null)
                throw new ArgumentNullException("column");
            if (column.ColumnType != ColumnType.Reference)
                throw new ArgumentException("The column type should be reference.");

            var refColumn = (RefColumn)column;

            // The column name
            if (!string.IsNullOrEmpty(Name) && Name != column.Name)
                column.Name = Name;

            // The column display name
            if (DisplayName != column.DisplayName)
                column.DisplayName = DisplayName;

            // The column description
            if (Description != column.Description)
                column.Description = Description;

            // The column order index
            if (OrderIndex.HasValue && OrderIndex != column.OrderIndex)
                column.OrderIndex = OrderIndex.Value;

            // The column nullability
            if (IsNotNull.HasValue && IsNotNull != column.IsNotNull)
                column.IsNotNull = IsNotNull.Value;

            // The column default value
            if (DefaultValue != refColumn.DefaultValue)
                refColumn.DefaultValue = DefaultValue;

            // The column reference checking
            if (HasReferenceChecking.HasValue && HasReferenceChecking != refColumn.HasReferenceChecking)
                refColumn.HasReferenceChecking = HasReferenceChecking.Value;
        }


        /// <summary>
        ///   Creates a data transfer object for the specified reference column.
        /// </summary>
        /// <param name="refColumn">The specified reference column.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="refColumn"/> is null.</exception>
        /// <returns>Returns the data transfer object for the specified reference column.</returns>
        public static RefColumnDto CreateFrom(RefColumn refColumn)
        {
            if (refColumn == null)
                throw new ArgumentNullException("refColumn");

            return new RefColumnDto
            {
                Name = refColumn.Name,
                DisplayName = refColumn.DisplayName,
                Description = refColumn.Description,
                OrderIndex = refColumn.OrderIndex,
                IsNotNull = refColumn.IsNotNull,
                DefaultValue = refColumn.DefaultValue,
                HasReferenceChecking = refColumn.HasReferenceChecking
            };
        }
    }
}
